using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.PackageManager.UI;
using System;
using System.Reflection;
using System.Linq;
using System.Threading.Tasks;
using CustomLitJson;

public class ApiInspector : EditorWindow
{
    [MenuItem("com.pzy.*/ApiInspector")]
    static void ShowWindow()
    {
        var window = EditorWindow.GetWindow<ApiInspector>("Api Inspector");
        window.Show();
    }

    Dictionary<Type, List<MethodInfo>> typeToMethodInfoListDic;
    void Search()
    {
        typeToMethodInfoListDic = new Dictionary<Type, List<MethodInfo>>();
        var list = ReflectionUtil.GetAttributedClassesInAllAssemblies<ApiAttribute>();
        foreach(var type in list)
        {
            var methodInfoList = type.GetMethods(BindingFlags.Static | BindingFlags.Public);
            typeToMethodInfoListDic[type] = methodInfoList.ToList();
        }
    }

    private void OnEnable()
    {
        if(typeToMethodInfoListDic == null)
        {
            this.Search();
        }
    }

    private void OnGUI()
    {
        if (this.typeToMethodInfoListDic != null)
        {
            foreach (var kv in this.typeToMethodInfoListDic)
            {
                DrawType(kv);
            }
        }
    }

    void DrawType(KeyValuePair<Type, List<MethodInfo>> kv)
    {
        var type = kv.Key;
        var name = type.Name;
        GUIStyle style = new GUIStyle(EditorStyles.foldout);
        var isFoldout = IsFoldout(name);
        var b = EditorGUILayout.Foldout(isFoldout, name, style);
        if(b != isFoldout)
        {
            SetIsFoldout(name, b);
        }
        
        if(b)
        {
            EditorGUI.indentLevel++;
            var methodList = kv.Value;
            foreach(var info in methodList)
            {
                DrawMethod(info);
            }
            EditorGUI.indentLevel--;
        }
    }

    string GetParamContent(string methodName, string name) => PlayerPrefs.GetString($"apiInspector.param.{methodName}.{name}", "");
    void SetParamContent(string methodName, string name, string value) => PlayerPrefs.SetString($"apiInspector.param.{methodName}.{name}", value);

    object ParseParam(string str, Type type)
    {
        if (type == typeof(int))
        {
            var b = int.TryParse(str, out var intValue);
            if (!b)
            {
                throw new Exception($"'{str}' can not parse to int");
            }
            return intValue;
        }
        else if (type == typeof(string))
        {
            return str;
        }
        else if(type == typeof(bool))
        {
            var b = bool.TryParse(str, out var boolValue);
            if (!b)
            {
                throw new Exception($"'{str}' can not parse to bool");
            }
            return boolValue;
        }
        else if (type == typeof(List<int>))
        {
            var parts = str.Split(",");
            var list = new List<int>();
            if (parts.Length == 1 && parts[0] == "")
            {
                return list;
            }
            foreach (var p in parts)
            {
                var intValue = int.Parse(p.Trim());
                list.Add(intValue);
            }
            return list;
        }
        else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            if (string.IsNullOrEmpty(str))
            {
                return null;
            }
            else
            {
                var argType = type.GenericTypeArguments[0];
                return ParseParam(str, argType);
            }
        }
        else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
        {
            return JsonMapper.Instance.ToObject(type, str);
        }
        else
        {
            throw new Exception("unsupport param type: " + type);
        }
    }

    object[] CreateInvokeParamObjectList(MethodInfo info)
    {
        var methodName = info.Name;
        var paramList = info.GetParameters();
        var ret = new object[paramList.Length];
        for(int i = 0; i < paramList.Length; i++)
        {
            var param = paramList[i];
            var paramName = param.Name;
            var paramType = param.ParameterType;
            var content = GetParamContent(methodName, paramName);
            content = content.Trim();
            var obj = ParseParam(content, paramType);
            ret[i] = obj;
        }
        return ret;
    }

    string WriteMethodDefines(MethodInfo info)
    {
        var methodName = info.Name;
        var retType = info.ReturnType;
        var retStr = WriteType(retType);
        return $"{retStr} {methodName}()";
    }

    void DrawParam(string methodName, ParameterInfo info)
    {
        var paramName = info.Name;
        var paramType = info.ParameterType;
        var content = GetParamContent(methodName, paramName);
        var label = $"{WriteType(paramType)} {paramName}";
        string postContent = null;
        if (paramType != typeof(bool))
        {
            postContent = EditorGUILayout.TextField(label, content);
        }
        else
        {
            bool.TryParse(content, out var value);
            value = EditorGUILayout.Toggle(label, value);
            postContent = value.ToString();
        }
        if (content != postContent)
        {
            SetParamContent(methodName, paramName, postContent);
        }
    }

    void DrawMethod(MethodInfo info)
    {
        var methodName = info.Name;
        var methodDefine = WriteMethodDefines(info);

        GUIStyle style = new GUIStyle(EditorStyles.foldout);
        var isFoldout = IsFoldout(methodName);
        var b = EditorGUILayout.Foldout(isFoldout, methodDefine, style);
        SetIsFoldout(methodName, b);
        if (!b)
        {
            return;
        }

        var paramList = info.GetParameters();
        EditorGUI.indentLevel++;
        foreach (var param in paramList)
        {
            DrawParam(methodName, param);
        }
        //GUILayout.Space(EditorGUI.indentLevel * 15 + 4);
        //if (GUILayout.Button("Invoke"))
        if (DrawButtonWithIndentLevel("Invoke"))
        {
            var paramObjList = CreateInvokeParamObjectList(info);
            var task = info.Invoke(null, paramObjList) as Task;
            if(task != null)
            {
                Await(task);
            }
        }
        EditorGUI.indentLevel--;
    }

    async void Await(Task task)
    {
        await task;
    }

    static bool DrawButtonWithIndentLevel(string content)
    {
        EditorGUILayout.BeginHorizontal();
        GUILayout.Space(EditorGUI.indentLevel * 15 + 4);
        var ret = false;
        if (GUILayout.Button(content))
        {
            ret = true;
        }
        EditorGUILayout.EndHorizontal();
        return ret;
    }

    static string WriteType(Type type)
    {
        if (type == typeof(int))
        {
            return "int";
        }
        else if (type == typeof(string))
        {
            return "string";
        }
        else if(type == typeof(bool))
        {
            return "bool";
        }
        else if (type == typeof(List<int>))
        {
            return "List<int>";
        }
        else if(type == typeof(void))
        {
            return "void";
        }
        else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            var argType = type.GetGenericArguments()[0];
            var argTypeName = WriteType(argType);
            return $"{argTypeName}?";
        }
        else if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>))
        {
            var argType = type.GetGenericArguments()[0];
            var argTypeName = WriteType(argType);
            return $"Task<{argTypeName}>";
        }
        else if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
        {
            var typeArray = type.GetGenericArguments();
            var keyType = typeArray[0];
            var valueType = typeArray[1];
            var keyTypeName = WriteType(keyType);
            var valueTypeName = WriteType(valueType);
            return $"Dictionary<{keyTypeName}, {valueTypeName}>";
        }
        return type.Name;
    }

    private Dictionary<string, bool> categoryFoldouts = new Dictionary<string, bool>();
    bool IsFoldout(string name) => PlayerPrefs.GetInt($"apiInspector.foldout.{name}", 0) == 1;
    void SetIsFoldout(string name, bool b) => PlayerPrefs.SetInt($"apiInspector.foldout.{name}", b ? 1 : 0);

}
